阅读更多
首先声明,我只是个程序员,不是专业的DBA,以下这篇文章是从一个问题的解决过程去写的,而不是一开始就给大家一个正确的结果,如果文中有不对的地方,请各位数据库大牛给予指正,以便我能够更好的处理此次业务。

项目背景
这是给某数据中心做的一个项目,项目难度之大令人发指,这个项目真正的让我感觉到了,商场如战场,而我只是其中的一个小兵,太多的战术,太多的高层之间的较量,太多的内幕了。具体这个项目的情况,我有空再写相关的博文出来。

这个项目是要求做环境监控,我们暂且把受监控的设备称为采集设备,采集设备的属性称为监控指标。项目要求:系统支持不少于10w个监控指标,每个监控指标的数据更新不大于20秒,存储延迟不超过120秒。那么,我们可以通过简单的计算得出较理想的状态——要存储的数据为:每分钟30w,每个小时1800w,也就是每天4亿3千两百万。而实际,数据量会比这个大5%左右。(实际上大部分是信息垃圾,可以通过数据压缩进行处理的,但是别人就是要搞你,能咋办)

上面是项目要求的指标,我想很多有不少大数据处理经验的同学都会呲之以鼻,就这么点?嗯,我也看了很多大数据处理的东西,但是之前没处理过,看别人是头头是道,什么分布式,什么读写分离,看起来确实很容易解决。但是,问题没这么简单,上面我说了,这是一个非常恶劣的项目,是一个行业恶性竞争典型的项目。


    [1、]没有更多的服务器,而是这个服务器除了搭配数据库、集中采集器(就是数据解析、告警、存储的程序),还要支持30w点的北向接口(SNMP),在程序没有优化之前CPU常年占用80%以上。因为项目要求要使用双机热备,为了省事,减少不必要的麻烦,我们把相关的服务放在一起,以便能够充分利用HA的特性(外部购买的HA系统)
    [2、]系统数据正确性要求极其变态,要求从底层采集系统到最上层的监控系统,一条数据都不能差
    我们的系统架构如下,可以看到,其中数据库压力非常之大,尤其在LevelA节点:



    [3、]硬件配置如下:
    CPU:英特尔® 至强® 处理器 E5-2609 (4核, 2.40GHz, 10MB, 6.4 GT/s)
    内存:4GB (2x2GB) DDR3 RDIMM Memory, 1333MHz,ECC
    硬盘:500GB 7200 RPM 3.5'' SATA3 硬盘,Raid5.
    [4、]数据库版本
    采用的是SQLServer2012标准版,HP提供的正版软件,缺少很多企业版的NB功能。

写入瓶颈
首先遇到的第一个拦路虎就是,我们发现现有的程序下,SQLServer根本处理不了这么多的数据量,具体情况是怎样的呢?

我们的存储结构
一般为了存储大量的历史数据,我们都会进行一个物理的分表,否则每天上百万条的记录,一年下来就是几亿条。因此,原来我们的表结构是这样的:
CREATE TABLE [dbo].[His20140822](
	[No] [bigint] IDENTITY(1,1) NOT NULL,
	[Dtime] [datetime] NOT NULL,
	[MgrObjId] [varchar](36) NOT NULL,
	[Id] [varchar](50) NOT NULL,
	[Value] [varchar](50) NOT NULL,
 CONSTRAINT [PK_His20140822] PRIMARY KEY CLUSTERED 
(
	[No] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

No作为唯一的标识、采集设备Id(Guid)、监控指标Id(varchar(50))、记录时间、记录值。并以采集设备Id和监控指标Id作为索引,以便快速查找。

批量写入
写入当时是用BulKCopy,没错,就是它,号称写入百万条记录都是秒级的
public static int BatchInert(string connectionString, string desTable, DataTable dt, int batchSize = 500)
    {
        using (var sbc = new SqlBulkCopy(connectionString, SqlBulkCopyOptions.UseInternalTransaction)
        {
            BulkCopyTimeout = 300,
            NotifyAfter = dt.Rows.Count,
            BatchSize = batchSize,
            DestinationTableName = desTable
        })
        {
            foreach (DataColumn column in dt.Columns)
                sbc.ColumnMappings.Add(column.ColumnName, column.ColumnName);
            sbc.WriteToServer(dt);
        }

        return dt.Rows.Count;
    }

存在什么问题?
上面的架构,在每天4千万的数据都是OK的。但是,调整为上述背景下的配置时,集中监控程序就内存溢出了,分析得知,接收的太多数据,放在了内存中,但是没有来得及写入到数据库中,最终导致了生成的数据大于消费的数据,导致内存溢出,程序无法工作。

瓶颈到底在哪里?
是因为RAID磁盘的问题?是数据结构的问题?是硬件的问题?是SQLServer版本的问题?是没有分区表的问题?还是程序的问题?

当时时间只有一个星期,一个星期搞不好,项目监管就要我们滚蛋了,于是,有了连续工作48小时的壮举,有了到处打电话求人的抓鸡……

但是,这个时候需要的是冷静,再冷静……SQLServer版本?硬件?目前都不大可能换的。RAID磁盘阵列,应该不是。那么到底是什么,真TM的冷静不下来。

大家可能体会不到现场那种紧张的气氛,其实过了这么久,我自己也都很难再回到那种情境。但是可以这么说,或许我们现在有了各种方法,或者处于局外人我们有更多思考,但是当一个项目压迫你快到放弃的时候,你那时的想法、考虑在现场环境因素的制约下,都可能出现重大的偏差。有可能让你快速的思考,也有可能思维停滞。有些同事在这种高压的环境下,甚至出现了更多的低级错误,思维已经完全乱了,效率更低了……36小时没有合眼,或者只在工地上(下雨天到处都是泥巴,干了的话到时都是泥灰)眯两三个小时,然后继续干,连续这么一个星期!或者还要继续!

很多人给了很多想法,但是好像有用,又好像没用。等等,为什么是“好像有用,又好像没用”?我隐隐约约中,好像抓住了一丝方向,到底是什么?对了,验证,我们现在是跑在现场环境下,之前没有问题,不代表现在的压力下没有问题,要在一个大型系统中分析这么个小功能,影响太大了,我们应该分解它。是的,是“单元测试”,就是单个方法的测试,我们需要验证每个函数,每个独立的步骤到底耗时在哪里?

逐步测试验证系统瓶颈
修改BulkCopy的参数
首先,我想到的是,修噶BulkCopy的各项参数,BulkCopyTimeout、BatchSize,不断的测试调整,结果总是在某个范围波动,实际并没有影响。或许会影响一些CPU计数,但是远远没有达到我的期望,写入的速度还是在5秒1w~2w波动,远远达不到要求20秒内要写20w的记录。

按采集设备存储
是的,上述结构按每个指标每个值为一条记录,是不是太多的浪费?那么按采集设备+采集时间作为一条记录是否可行?问题是,怎么解决不同采集设备属性不一样的问题?这时,一个同事发挥才能了,监控指标+监控值可以按XML格式存储。哇,还能这样?查询呢,可以用for XML这种形式。
于是有了这种结构:
引用
No、MgrObjId、Dtime、XMLData

结果验证,比上面的稍微好点,但是不是太明显。

数据表分区???
那个时候还没有学会这个技能,看了下网上的文章,好像挺复杂的,时间不多了,不敢尝试。

停止其他程序
我知道这个肯定是不行的,因为软件、硬件的架构暂时没法修改。但是我希望验证是不是这些因素影响的。结果发现,提示确实明显,但是还是没有达到要求。

难道是SQLServer的瓶颈?
没辙了,难道这就是SQLServer的瓶颈?上网查了下相关的资料,可能是IO的瓶颈,尼玛,还能怎么办,要升级服务器,要更换数据库了吗,但是,项目方给吗?

等等,好像还有个东西,索引,对索引!索引的存在会影响插入、更新

去掉索引
是的,去掉索引之后查询肯定慢,但是我必须先验证去掉索引是否会加快写入。如果果断把MgrObjId和Id两个字段的索引去掉。

运行,奇迹出现了,每次写入10w条记录,在7~9秒内完全可以写入,这样就达到了系统的要求。

查询怎么解决?
一个表一天要4亿多的记录,这是不可能查询的,在没有索引的情况下。怎么办!?我又想到了我们的老办法,物理分表。是的,原来我们按天分表,那么我们现在按小时分表。那么24个表,每个表只需存储1800w条记录左右。

然后查询,一个属性在一个小时或者几个小时的历史记录。结果是:慢!慢!!慢!!!去掉索引的情况下查询1000多万的记录根本是不可想象的。还能怎么办?

继续分表,我想到了,我们还可以按底层的采集器继续分表,因为采集设备在不同的采集器中是不同的,那么我们查询历史曲线时,只有查单个指标的历史曲线,那么这样就可以分散在不同的表中了。

说干就干,结果,通过按10个采集嵌入式并按24小时分表,每天生成240张表(历史表名类似这样:His_001_2014112615),终于把一天写入4亿多条记录并支持简单的查询这个问题给解决掉了!!!

查询优化
在上述问题解决之后,这个项目的难点已经解决了一半,项目监管也不好意思过来找茬,不知道是出于什么样的战术安排吧。

过了很长一段时间,到现在快年底了,问题又来了,就是要拖死你让你在年底不能验收其他项目。

这次要求是这样的:因为上述是模拟10w个监控指标,而现在实际上线了,却只有5w个左右的设备。那么这个明显是不能达到标书要求的,不能验收。那么怎么办呢?这些聪明的人就想,既然监控指标减半,那么我们把时间也减半,不就达到了吗:就是说按现在5w的设备,那你要10s之内入库存储。我勒个去啊,按你这个逻辑,我们如果只有500个监控指标,岂不是要在0.1秒内入库?你不考虑下那些受监控设备的感想吗?

但是别人要玩你,你能怎么办?接招呗。结果把时间降到10秒之后,问题来了,大家仔细分析上面逻辑可以知道,分表是按采集器分的,现在采集器减少,但是数量增加了,发生什么事情呢,写入可以支持,但是,每张表的记录接近了400w,有些采集设备监控指标多的,要接近600w,怎么破?

于是技术相关人员开会讨论相关的举措。

在不加索引的情况下怎么优化查询?
有同事提出了,where子句的顺序,会影响查询的结果,因为按你刷选之后的结果再处理,可以先刷选出一部分数据,然后继续进行下一个条件的过滤。听起来好像很有道理,但是SQLServer查询分析器不会自动优化吗?原谅我是个小白,我也是感觉而已,感觉应该跟VS的编译器一样,应该会自动优化吧。

具体怎样,还是要用事实来说话:

结果同事修改了客户端之后,测试反馈,有较大的改善。我查看了代码:



难道真的有这么大的影响?等等,是不是忘记清空缓存,造成了假象?
于是让同事执行下述语句以便得出更多的信息:
--优化之前
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS

SET STATISTICS IO ON
select Dtime,Value from dbo.his20140825 WHERE  Dtime>='' AND Dtime<='' AND MgrObjId='' AND Id=''
SET STATISTICS IO OFF

--优化之后
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS

SET STATISTICS IO ON
select Dtime,Value from dbo.his20140825 WHERE MgrObjId='' AND Id='' AND Dtime>='' AND Dtime<=''
SET STATISTICS IO OFF




优化之前反而更好了?

仔细查看IO数据,发现,预读是一样的,就是说我们要查询的数据记录都是一致的,物理读、表扫描也是一直的。而逻辑读取稍有区别,应该是缓存命中数导致的。也就是说,在不建立索引的情况下,where子句的条件顺序,对查询结果优化作用不明显。

那么,就只能通过索引的办法了。

建立索引的尝试
建立索引不是简单的事情,是需要了解一些基本的知识的,在这个过程中,我走了不少弯路,最终才把索引建立起来。

下面的实验基于以下记录总数做的验证:



按单个字段建立索引
这个想法,主要是受我建立数据结构影响的,我内存中的数据结构为:Dictionary<MgrObjId,Dictionary<Id,Property>>。我以为先建立MgrObjId的索引,再建立Id的索引,SQLServer查询时,就会更快。



先按MgrObjId建立索引,索引大小为550M,耗时5分25秒。结果,如上图的预估计划一样,根本没有起作用,反而更慢了。

按多个条件建立索引
OK,既然上面的不行,那么我们按多个条件建立索引又如何?CREATE NONCLUSTERED INDEX Idx_His20141008 ON dbo.his20141008(MgrObjId,Id,Dtime)

结果,查询速度确实提高了一倍:



等等,难道这就是索引的好处?花费7分25秒,用1.1G的空间换取来的就是这些?肯定是有什么地方不对了,于是开始翻查资料,查看一些相关书籍,最终,有了加大的进展。

正确的建立索引
首先,我们需要明白几个索引的要点:

  • 索引之后,按索引字段重复最少的来排序,会达到最优的效果。以我们的表来说,如果建立了No的聚集索引,把No放在where子句的第一位是最佳的,其次是Id,然后是MgrObjId,最后是时间,时间索引如果表是一个小时的,最好不要用
  • where子句的顺序决定了查询分析器是否使用索引来查询。比如建立了MgrObjId和Id的索引,那么where MgrObjId='' and Id='' and Dtime=''就会采用索引查找,而where Dtime='' and MgrObjId='' and Id=''则不一定会采用索引查找。
  • 把非索引列的结果列放在包含列中。因为我们条件是MgrObjId和Id以及Dtime,因此返回结果中只需包含Dtime和Value即可,因此把Dtime和Value放在包含列中,返回的索引结果就有这个值,不用再查物理表,可以达到最优的速度。

跟上述几点原则,我们建立以下的索引:CREATE NONCLUSTERED INDEX Idx_His20141008 ON dbo.his20141008(MgrObjId,Id) INCLUDE(Value,Dtime)

耗费时间为:6分多钟,索引大小为903M。

我们看看预估计划:



可以看到,这里完全使用了索引,没有额外的消耗。而实际执行的结果,1秒都不到,竟然不用一秒就在1100w的记录中把结果筛选了出来!!帅呆了!!

怎么应用索引?
既然写入完成了、读取完成了,怎么结合呢?我们可以把一个小时之前的数据建立索引,当前一个小时的数据就不建立索引。也就是,不要再创建表的时候建立索引!!

还能怎么优化
可以尝试读写分离,写两个库,一个是实时库,一个是只读库。一个小时内的数据查询实时库,一个小时之前的数据查询只读库;只读库定时存储,然后建立索引;超过一个星期的数据,进行分析处理再存储。这样,无论查询什么时间段的数据,都能够正确处理了——一个小时之内的查询实时库,一个小时到一个星期内的查询只读库,一个星期之前的查询报表库。

如果不需要物理分表,则在只读库中,定时重建索引即可。

总结
如何在SQLServer中处理亿万级别的数据(历史数据),可以按以下方面进行:
  • 去掉表的所有索引
  • 用SqlBulkCopy进行插入
  • 分表或者分区,减少每个表的数据总量
  • 在某个表完全写完之后再建立索引
  • 正确的指定索引字段
  • 把需要用到的字段放到包含索引中(在返回的索引中就包含了一切)
  • 查询的时候只返回所需的字段
  • 大小: 31.7 KB
  • 大小: 21.2 KB
  • 大小: 113 KB
  • 大小: 14.9 KB
  • 大小: 179.7 KB
  • 大小: 148.2 KB
  • 大小: 13.3 KB
来自: 码到成功
4
0
评论 共 4 条 请登录后发表评论
4 楼 heyucong 2015-01-19 15:57
不错,解决方法的思路值得学习

不过如果条件允许,可以考虑使用实时数据库,国外有PI 和eDNA等实时数据库,电力行业使用得比较多

当然,以上只是空谈而已,仅提供一种思路。
3 楼 一个java程序员 2014-12-24 16:17
通篇看完了, 我觉得 楼主学到了很多知识。

给攒,但是我觉得 楼主应该不是高手, 原谅我的无知吧。

很多业务都跟楼主的差不多吗,就是 大量的插入

然后作为历史数据,   或者统计  做成报表数据,  或者实时查询。


2 楼 aa1asdasd 2014-12-07 22:30
你们应该把你们项目监管给炒掉,然后把这个项目搞黄他。这么糟糕的项目应该让它死掉。
1 楼 Bining 2014-12-01 12:52
不错。从问题出发的文章,总会给人带来一些思路!

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 人工智能普及之JAVA & AI 课程第一课

    JAVA & AI 课程第一课 未来已来,2023注定最火的是AI,大家也许听说过AI,AIGC、GPT。那么这些专业名词都是什么意思?又将对我们未来的生活产生什么影响呢? 一.概念篇(扫盲) AI 人工智能(Artificial Intelligence)...

  • 程序员用学位证吗_没有学位如何成为一名优秀的Java程序员

    但是,如何成为一名优秀的Java程序员不是一个简单的问题? 您不需要任何正式培训。 您无需坐在教室里就可以获得文凭。 如果没有证明这一事实的学位,您当然可以成为一名优秀的Java程序员。 不,您所需要的只是...

  • 如何写出让面试官眼前一亮的Java开发简历(附模板)

    对于程序员找工作而言,简历可以说是整个面试过程中至关重要的一环,HR每天要过几百上千份简历,如何在几秒内就让HR记住你的简历,让面试官眼前一亮? 总体来说,对于程序员的简历,一切从简,不要弄的花里胡哨,...

  • 对于自学Java的人来说,如何系统的,全方面的学习Java?

    我今年41岁,使用Java开发将近20年了,目前还在技术一线。 虽然我是科班出身,但其实十多年走过来,无论是Java语言本身,还是相伴的工具和框架都...但好在Java学习,总的来说还是重点突出、难点分明的,也确实存在一.

  • Java蓝桥杯

    2022年第十三届蓝桥杯大赛软件类决赛Java研究生组真题 - 题库 - C语言网 2021年蓝桥杯第十二届省赛及国赛真题 - 题库 - C语言网 2020年蓝桥杯第十一届省赛及国赛真题 - 题库 - C语言网 2019年蓝桥杯第十届省赛及...

  • 《Java并发编程实战》【第一部分 基础知识】

    简介1.1 并发简史1.2 线程的优势1.2.1 发挥多处理器的强大能力1.2.2 建模的简单性1.2.3 异步事件的简化处理1.2.4 响应更灵敏的用户界面1.3 线程带来的风险1.3.1 安全性问题1.3.2 活跃性问题1.3.3 性能问题1.4 线程...

  • Java经验者的面经

    阿里面试回来,和Java程序员谈一谈 ni掌柜 2016-05-03 19:20:09 举报 阅读数:915 回答阿里社招面试如何准备,顺便谈谈对于Java程序猿学习当中各个阶段的建议 文/左潇龙 引言  

  • 一位35岁Java工程师给我的启示,看完真的心酸!

    那是我第一天来到公司,公司专门为我们实习生分配了导师,而且专门为我们调整了位置,我把东西都准备好之后,开始估摸着怎么去融入公司,怎么去了解公司的技术。因为我属于Android转Java后台,很紧张,我怕我不能够...

  • 这几个棘手的面试常见问题,如何高情商的回答?

    我是架构精进之路,大厂架构师,CSDN博客专家,点击上方“关注”,坚持每天为你分享技术干货,私信我回复“01”,送你一份程序员成长进阶大礼包。面试过程中涉及非技术本身的问题,程序员是否...

  • 基本Java资源

    自1995年将Java平台引入整个编程社区以来,Java平台已经远远超出了早期Java专家和传道者所想象的Java世界所具有的“无处不在的小应用程序”的愿景。 取而代之的是,Java世界上升到了Swing,围绕servlet合并,加入...

  • 2年工作经验的Java程序员面试经历

    时光转瞬即逝,一年时间已经过去,自己准备跳槽了,请了几天假,面试了几家公司之后,拿到了一份offer,有了一点儿小小的心得,记录下来分享给搞Java的朋友,面试主要是几个部分内容。 很多公司会要求笔试,笔试的...

  • 小猿圈之浅析Java面试需要注意的方面?

    毕业季已经来临,又讲会有一大批刚出校门的学生面临找工作,那么作为计算机毕业的学生如果想要面试java岗位,那么你可以好好看看了,小猿圈中的学姐得好好告诉你一些面试技巧了,能看到这篇文章的同学,详细了解一下...

  • Supercell带给我们的启示

    估计很多读者朋友们都玩过《部落冲突》、《皇室战争》这些手游,他都出自于一家来自芬兰的游戏公司Supercell。他旗下的5个爆款游戏,《卡通农场》、《部落冲突》、《海岛奇兵》、《皇室战争》...

  • java工程师工作期望_担任软件工程师时的期望

    java工程师工作期望This article is based on my own experience and opinions. 本文基于我自己的经验和观点。 您将解决问题 (You will solve problems) Software engineers (or developers, programmers, coders ...

  • Java英雄:丹·艾伦

    “ Java英雄 ”系列休息了很长时间。 老实说,我想即使有很多人想在这里收录,它也可能会以虚无收场。 其中之一是丹。 我第一次要求他捐款已经将近一年半了,与此同时发生的一切,让我不再有任何答案就让我安心了。 ...

  • 求职历程(一)——和陌生人会面

    这时的简历有三个版本,第一份是表格的,2页;第二个,是中英文,各2页;第三个,撤去表格,中英文各2页。这个阶段有兴奋却不见伤心,因为不知道求职意味着什么,而且这阶段想的是找个实习,并没想到工

  • unite 开发_在Unite Nordic振兴您的游戏开发

    来到瑞典马尔默,与许多令人难以置信的欧洲游戏开发社区会面,这些社区正在帮助塑造行业并与Unity一起制作出色的游戏。 I’ll see you there, 我会在那里见你, David 大卫 翻译自: ...

  • 好程序员Java培训分享面试Java的注意事项

    好程序员Java培训分享面试Java的注意事项,现在的程序员真的有很多,不论是Java还是H5或者是Linux等开发技术都有不少的人在学习,想要成为一名专业的Java开发工程师必须要经过专业的学习,因此参加培训的人有很多,...

  • 回顾:Java影响者考虑了Java 11和JDK的状态

    由于Java 11将获得长期支持,因此这是一个重要的里程碑。 对于尚未从Java 8迁移的开发人员,JDK 11的发行版是紧随其后的新牧场和新功能的最佳时机。 最近,我们采访了Java的主要影响者,询问了有关新发行版,弃用...

  • 机器学习 参会理由_您应该学习如何开发视频游戏的10个理由

    机器学习 参会理由 如果您问任何软件开发人员,他们很有可能 尝试过游戏开发。 看起来很自然 因为它使用了许多相同的技能。 更重要的是,程序员来了 最有抱负的游戏制造商缺乏的知识:如何 编写好的代码并创建有效的...

Global site tag (gtag.js) - Google Analytics